﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ComputerArchitecture.Core;
using ComputerArchitecture.Core.Logging;
using ComputerArchitecture.Numbers;
using ComputerArchitecture.Units.Integers;
using ComputerArchitecture.Units.Integers.Signed.Implementations;
using ComputerArchitecture.Units.Integers.Signed.Interfaces;
using ComputerArchitecture.Units.Integers.Unsigned.Implementations;
using ComputerArchitecture.Units.Integers.Unsigned.Interfaces;
using ComputerArchitecture.Units.Interfaces;

namespace ComputerArchitecture.Units.Float
{
    public class FloatMultiplyUnit : LoggingObject, IFloatMultiplyUnit
    {
        #region Fields

        private readonly ISignedAdderUnit _signedAdderUnit;
        private readonly ISignedSubstractUnit _signedSubstractUnit;
        private readonly IUnsignedMultiplyUnit _unsignedMultiplyUnit;


        #endregion

        #region Constructors

        public FloatMultiplyUnit(ILoggingService loggingService, ISignedAdderUnit signedAdderUnit, ISignedSubstractUnit signedSubstractUnit, IUnsignedMultiplyUnit unsignedMultiplyUnit)
            : base(loggingService)
        {
            ArgumentValidator.NullValidate(signedAdderUnit, "signedAdderUnit");
            _signedAdderUnit = signedAdderUnit;
            ArgumentValidator.NullValidate(signedSubstractUnit, "signedSubstractUnit");
            _signedSubstractUnit = signedSubstractUnit;
            ArgumentValidator.NullValidate(unsignedMultiplyUnit, "unsignedMultiplyUnit");
            _unsignedMultiplyUnit = unsignedMultiplyUnit;
        }

        public FloatMultiplyUnit(ILoggingService loggingService)
            : this(loggingService, new SignedAdderUnit(loggingService), new SignedSubstractUnit(loggingService), new UnsignedMultiplyUnit(loggingService))
        {
        }

        #endregion

        private static IUnsignedBitNumber RemoveFirstBit(IBitNumber mantissa)
        {
            IUnsignedBitNumber value = new UnsignedBitNumber(mantissa.Size - 1);
            mantissa.CopyTo(value, 0, 1, value.Size);
            return value;
        }

        private static IUnsignedBitNumber GetUnsignedOffset(int size)
        {
            IUnsignedBitNumber v = new UnsignedBitNumber(size);
            for (int i = 1; i < size; ++i)
            {
                v[i].Set();
            }
            return v;
        }

        private static ISignedBitNumber GetSignedOffset(int size)
        {
            ISignedBitNumber v = new SignedBitNumber(size);
            for (int i = 1; i < size; ++i)
            {
                v[i].Set();
            }
            return v;
        }

        private static ISignedBitNumber MantissaToSigned(IUnsignedBitNumber mantissaWithImplicit, Bit sign, out bool hasError)
        {
            ISignedBitNumber value = new SignedBitNumber(mantissaWithImplicit.Size + 1);
            mantissaWithImplicit.CopyTo(value, 1, 0, mantissaWithImplicit.Size);
            hasError = false;
            if (sign.IsSet)
            {
                ILoggingService log = new NLogFacadeLoggingService();
                log.DisableLogging();
                NegateSpecialCase specialCase;
                value = new SignedNegateUnit(log).Negate(value, out specialCase, out hasError);
                log.EnableLogging();
            }
            return value;
        }

        public IFloatPointNumber Multiply(IFloatPointNumber first, IFloatPointNumber second)
        {
            #region Special cases

            ArgumentValidator.NullValidate(first, "first");
            ArgumentValidator.NullValidate(second, "second");
            // validate size
            if (first.Mantissa.Size != second.Mantissa.Size || first.Exponent.Size != second.Exponent.Size)
            {
                throw new IncompatibleSizeException();
            }
            // check if first is zero
            if (first.IsZero)
            {
                this.LoggingService.LogInfo("First operand equals zero. Return zero");
                return FloatPointNumber.GetPositiveZero(first.Exponent.Size, first.Mantissa.Size);
            }
            if (second.IsZero)
            {
                this.LoggingService.LogInfo("Second operand equals zero. Return zero");
                return FloatPointNumber.GetPositiveZero(second.Exponent.Size, second.Mantissa.Size);
            }
            // if any is nan - return nan
            if (first.IsNaN)
            {
                this.LoggingService.LogInfo("First operand equals NaN. Return NaN");
                return FloatPointNumber.GetQuietNaN(first.Exponent.Size, first.Mantissa.Size);
            }
            if (second.IsNaN)
            {
                this.LoggingService.LogInfo("Second operand equals NaN. Return NaN");
                return FloatPointNumber.GetQuietNaN(second.Exponent.Size, second.Mantissa.Size);
            }
            if (first.IsPositiveInfinity)
            {
                this.LoggingService.LogInfo("First is positive infinity");
                if (second.IsZero)
                {
                    this.LoggingService.LogInfo("Second is zero. Return NaN");
                    return FloatPointNumber.GetQuietNaN(second.Exponent.Size, second.Mantissa.Size);
                }
                if (second.IsNegativeInfinity)
                {
                    this.LoggingService.LogInfo("Second is negative infinity. Return negative infinity");
                    return FloatPointNumber.GetNegativeInfinity(first.Exponent.Size, first.Mantissa.Size);
                }
                this.LoggingService.LogInfo("Return positiveInfinity");
                return FloatPointNumber.GetPositiveInfinity(first.Exponent.Size, first.Mantissa.Size);
            }

            if (first.IsNegativeInfinity)
            {
                this.LoggingService.LogInfo("First is negative infinity");
                if (second.IsZero)
                {
                    this.LoggingService.LogInfo("Second is zero. Return NaN");
                    return FloatPointNumber.GetQuietNaN(second.Exponent.Size, second.Mantissa.Size);
                }
                if (second.IsPositiveInfinity)
                {
                    this.LoggingService.LogInfo("Second is positive infinity. Return negative infinity");
                    return FloatPointNumber.GetNegativeInfinity(first.Exponent.Size, first.Mantissa.Size);
                }
                this.LoggingService.LogInfo("Return positiveInfinity");
                return FloatPointNumber.GetPositiveInfinity(first.Exponent.Size, first.Mantissa.Size);
            }

            // todo: some special cases

            #endregion

            // add exponents

            this.LoggingService.LogInfo("Calculate sign bit as \'xor\'");
            Bit resultBit = first.SignBit ^ second.SignBit;
            this.LoggingService.LogInfo("Sign bit: {0}", resultBit.ToString());

            this.LoggingService.LogInfo("Sum exponents");
            IUnsignedBitNumber unsignedOffset = GetUnsignedOffset(first.Exponent.Size);
            ISignedBitNumber firstExponent = first.Exponent.ToComplement(unsignedOffset),
                             secondExponent = second.Exponent.ToComplement(unsignedOffset);
            this.LoggingService.LogInfo("First complement exponent: {0}", firstExponent);
            this.LoggingService.LogInfo("Second complement exponent: {0}", secondExponent);
            bool overflow = false;
            _signedAdderUnit.LoggingService.DisableLogging();
            ISignedBitNumber resultExponent = _signedAdderUnit.Add(firstExponent, secondExponent, out overflow);
            _signedAdderUnit.LoggingService.EnableLogging();
            // if overflow - return +inf
            if (overflow)
            {
                // 0 + 0 = 1?? positive + positive = inf
                if (resultExponent[0].IsSet)
                {
                    this.LoggingService.LogWarning("Overflow after adding exponents");
                    if (resultBit.IsSet)
                    {
                        this.LoggingService.LogInfo("Return negative infinity");
                        return FloatPointNumber.GetNegativeInfinity(first.Exponent.Size, first.Mantissa.Size);
                    }
                    this.LoggingService.LogInfo("Return positive infinity");
                    return FloatPointNumber.GetPositiveInfinity(first.Exponent.Size, first.Mantissa.Size);
                }
                else
                {
                    this.LoggingService.LogWarning("Exponent underflow detected");
                    if (resultBit.IsSet)
                    {
                        this.LoggingService.LogInfo("Return negative zero");
                        return FloatPointNumber.GetNegativeZero(first.Exponent.Size, first.Mantissa.Size);
                    }
                    this.LoggingService.LogInfo("Return positive zero");
                    return FloatPointNumber.GetPositiveZero(first.Exponent.Size, first.Mantissa.Size);
                }
            }
            this.LoggingService.LogInfo("Sum of exponent: {0}", resultExponent);
           
            // multiply mantissas
            bool hasError;
          
            IUnsignedBitNumber implicitFirstMantissa = first.GetImplicitMantissa(),
                               implicitSecondMantissa = second.GetImplicitMantissa();

            this.LoggingService.LogInfo("Multiply mantissas as unsigned integer");
            _unsignedMultiplyUnit.LoggingService.DisableLogging();
            var result = _unsignedMultiplyUnit.Multiply(implicitFirstMantissa, implicitSecondMantissa);
            _unsignedMultiplyUnit.LoggingService.EnableLogging();
            // xx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
            if (result[0].IsSet)
            {
                _signedAdderUnit.LoggingService.DisableLogging();
                resultExponent = _signedAdderUnit.Inc(resultExponent, out overflow);
                _signedAdderUnit.LoggingService.EnableLogging();
                if (overflow)
                {
                    this.LoggingService.LogWarning("Exponent overflow detected. Return infinity");
                    if (resultBit.IsSet)
                    {
                        return FloatPointNumber.GetNegativeInfinity(first.Exponent.Size, first.Mantissa.Size);
                    }
                    return FloatPointNumber.GetPositiveInfinity(first.Exponent.Size, first.Mantissa.Size);
                }
            }
            else
            {
                Bit carryBit;
                result.LogicShiftLeft(1, out carryBit);
            }
            IUnsignedBitNumber newMantissa = new UnsignedBitNumber(implicitFirstMantissa.Size);
            result.CopyTo(newMantissa, 0, 0, implicitFirstMantissa.Size);
            this.LoggingService.LogInfo("result mantissa: {0}", newMantissa);
            while (!newMantissa[0].IsSet)
            {
                Bit carryBit;
                _signedSubstractUnit.LoggingService.DisableLogging();
                resultExponent = _signedSubstractUnit.Dec(resultExponent, out overflow);
                _signedSubstractUnit.LoggingService.EnableLogging();
                this.LoggingService.LogInfo("New exponent: {0}", resultExponent);
                newMantissa.LogicShiftLeft(1, out carryBit);
                this.LoggingService.LogInfo("New mantissa: {0}", newMantissa);
            }
            return new FloatPointNumber(resultBit, resultExponent.ToOffseted(GetSignedOffset(first.Exponent.Size)), RemoveFirstBit(newMantissa));

            throw new NotImplementedException();
        }
    }
}
